/***
 *
 * Portions Copyright (c) 2007 Paul Hammant
 * Portions copyright (c) 2000-2007 INRIA, France Telecom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package cn.org.rapid_framework.generator.util.paranamer;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

/**
 * An ASM-based implementation of Paranamer. It relies on debug information compiled
 * with the "-g" javac option to retrieve parameter names.
 * <p/>
 * Portions of this source file are a fork of ASM.
 *
 * @author Guilherme Silveira
 * @author Paul Hammant
 */
public class BytecodeReadingParanamer implements Paranamer {

    private static final Map<String, String> primitives = new HashMap<String, String>() {

	{
	    put("int", "I");
	    put("boolean", "Z");
	    put("char", "C");
	    put("short", "B");
	    put("float", "F");
	    put("long", "J");
	    put("double", "D");
	}
    };

    public String[] lookupParameterNames(AccessibleObject methodOrConstructor) {

	return lookupParameterNames(methodOrConstructor, true);
    }

    public String[] lookupParameterNames(AccessibleObject methodOrCtor,
	    boolean throwExceptionIfMissing) {

	Class<?>[] types = null;
	Class<?> declaringClass = null;
	String name = null;
	if (methodOrCtor instanceof Method) {
	    Method method = (Method) methodOrCtor;
	    types = method.getParameterTypes();
	    name = method.getName();
	    declaringClass = method.getDeclaringClass();
	} else {
	    Constructor<?> constructor = (Constructor<?>) methodOrCtor;
	    types = constructor.getParameterTypes();
	    declaringClass = constructor.getDeclaringClass();
	    name = "<init>";
	}

	if (types.length==0) {
	    return EMPTY_NAMES;
	}
	InputStream byteCodeStream = getClassAsStream(declaringClass);
	if (byteCodeStream==null) {
	    if (throwExceptionIfMissing) {
		throw new ParameterNamesNotFoundException("Unable to get class bytes");
	    } else {
		return Paranamer.EMPTY_NAMES;
	    }
	}
	try {
	    ClassReader reader = new ClassReader(byteCodeStream);
	    TypeCollector visitor = new TypeCollector(name, types, throwExceptionIfMissing);
	    reader.accept(visitor);
	    String[] parameterNamesForMethod = visitor.getParameterNamesForMethod();
	    try {
		byteCodeStream.close();
	    } catch (IOException e) {
	    }
	    return parameterNamesForMethod;
	} catch (IOException e) {
	    if (throwExceptionIfMissing) {
		throw new ParameterNamesNotFoundException("IoException while reading class bytes",
			e);
	    } else {
		return Paranamer.EMPTY_NAMES;
	    }
	}
    }

    private InputStream getClassAsStream(Class<?> clazz) {

	ClassLoader classLoader = clazz.getClassLoader();
	if (classLoader==null) {
	    classLoader = ClassLoader.getSystemClassLoader();
	}
	return getClassAsStream(classLoader, clazz.getName());
    }

    private InputStream getClassAsStream(ClassLoader classLoader, String className) {

	String name = className.replace('.', '/')+".class";
	// better pre-cache all methods otherwise this content will be loaded
	// multiple times
	InputStream asStream = classLoader.getResourceAsStream(name);
	if (asStream==null) {
	    asStream = BytecodeReadingParanamer.class.getResourceAsStream(name);
	}
	return asStream;
    }

    /**
     * The type collector waits for an specific method in order to start a method
     * collector.
     *
     * @author Guilherme Silveira
     */
    private static class TypeCollector {

	private static final String COMMA = ",";

	private final String methodName;

	private final Class<?>[] parameterTypes;
	private final boolean throwExceptionIfMissing;

	private MethodCollector collector;

	private TypeCollector(String methodName, Class<?>[] parameterTypes,
		boolean throwExceptionIfMissing) {

	    this.methodName = methodName;
	    this.parameterTypes = parameterTypes;
	    this.throwExceptionIfMissing = throwExceptionIfMissing;
	    this.collector = null;
	}

	public MethodCollector visitMethod(int access, String name, String desc) {

	    // already found the method, skip any processing
	    if (collector!=null) {
		return null;
	    }
	    // not the same name
	    if (!name.equals(methodName)) {
		return null;
	    }
	    Type[] argumentTypes = Type.getArgumentTypes(desc);
	    int longOrDoubleQuantity = 0;
	    for (Type t : argumentTypes) {
		if (t.getClassName().equals("long")||t.getClassName().equals("double")) {
		    longOrDoubleQuantity++;
		}
	    }
	    int paramCount = argumentTypes.length;
	    // not the same quantity of parameters
	    if (paramCount!=this.parameterTypes.length) {
		return null;
	    }
	    for (int i = 0; i<argumentTypes.length; i++) {
		if (!correctTypeName(argumentTypes, i).equals(this.parameterTypes[i].getName())) {
		    return null;
		}
	    }
	    this.collector = new MethodCollector((Modifier.isStatic(access) ? 0 : 1),
		    argumentTypes.length+longOrDoubleQuantity);
	    return collector;
	}

	private String correctTypeName(Type[] argumentTypes, int i) {

	    String s = argumentTypes[i].getClassName();
	    // array notation needs cleanup.
	    if (s.endsWith("[]")) {
		String prefix = s.substring(0, s.length()-2);
		if (primitives.containsKey(prefix)) {
		    s = "["+primitives.get(prefix);
		} else {
		    s = "[L"+prefix+";";
		}
	    }
	    return s;
	}

	private String[] getParameterNamesForMethod() {

	    if (collector==null) {
		return Paranamer.EMPTY_NAMES;
	    }
	    if (!collector.isDebugInfoPresent()) {
		if (throwExceptionIfMissing) {
		    throw new ParameterNamesNotFoundException("Parameter names not found for "
			    +methodName);
		} else {
		    return Paranamer.EMPTY_NAMES;
		}
	    }
	    return collector.getResult().split(COMMA);
	}

    }

    /**
     * Objects of this class collects information from a specific method.
     *
     * @author Guilherme Silveira
     */
    private static class MethodCollector {

	private final int paramCount;

	private final int ignoreCount;

	private int currentParameter;

	private final StringBuffer result;

	private boolean debugInfoPresent;

	private MethodCollector(int ignoreCount, int paramCount) {

	    this.ignoreCount = ignoreCount;
	    this.paramCount = paramCount;
	    this.result = new StringBuffer();
	    this.currentParameter = 0;
	    // if there are 0 parameters, there is no need for debug info
	    this.debugInfoPresent = paramCount==0;
	}

	public void visitLocalVariable(String name, int index) {

	    if (index>=ignoreCount&&index<ignoreCount+paramCount) {
		if (!name.equals("arg"+currentParameter)) {
		    debugInfoPresent = true;
		}
		result.append(',');
		result.append(name);
		currentParameter++;
	    }
	}

	private String getResult() {

	    return result.length()!=0 ? result.substring(1) : "";
	}

	private boolean isDebugInfoPresent() {

	    return debugInfoPresent;
	}

    }

    /***
     * Portions Copyright (c) 2007 Paul Hammant
     * Portions copyright (c) 2000-2007 INRIA, France Telecom
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions
     * are met:
     * 1. Redistributions of source code must retain the above copyright
     *    notice, this list of conditions and the following disclaimer.
     * 2. Redistributions in binary form must reproduce the above copyright
     *    notice, this list of conditions and the following disclaimer in the
     *    documentation and/or other materials provided with the distribution.
     * 3. Neither the name of the copyright holders nor the names of its
     *    contributors may be used to endorse or promote products derived from
     *    this software without specific prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
     * THE POSSIBILITY OF SUCH DAMAGE.
     */

    /**
     * A Java class parser to make a Class Visitor visit an existing class.
     * This class parses a byte array conforming to the Java class file format and
     * calls the appropriate visit methods of a given class visitor for each field,
     * method and bytecode instruction encountered.
     *
     * @author Eric Bruneton
     * @author Eugene Kuleshov
     */
    private static class ClassReader {

	/**
	 * The class to be parsed. <i>The content of this array must not be
	 * modified. This field is intended for Attribute sub classes, and
	 * is normally not needed by class generators or adapters.</i>
	 */
	public final byte[] b;

	/**
	 * The start index of each constant pool item in {@link #b b}, plus one.
	 * The one byte offset skips the constant pool item tag that indicates its
	 * type.
	 */
	private final int[] items;

	/**
	 * The String objects corresponding to the CONSTANT_Utf8 items. This cache
	 * avoids multiple parsing of a given CONSTANT_Utf8 constant pool item,
	 * which GREATLY improves performances (by a factor 2 to 3). This caching
	 * strategy could be extended to all constant pool items, but its benefit
	 * would not be so great for these items (because they are much less
	 * expensive to parse than CONSTANT_Utf8 items).
	 */
	private final String[] strings;

	/**
	 * Maximum length of the strings contained in the constant pool of the
	 * class.
	 */
	private final int maxStringLength;

	/**
	 * Start index of the class header information (access, name...) in
	 * {@link #b b}.
	 */
	public final int header;

	/**
	 * The type of CONSTANT_Fieldref constant pool items.
	 */
	final static int FIELD = 9;

	/**
	 * The type of CONSTANT_Methodref constant pool items.
	 */
	final static int METH = 10;

	/**
	 * The type of CONSTANT_InterfaceMethodref constant pool items.
	 */
	final static int IMETH = 11;

	/**
	 * The type of CONSTANT_Integer constant pool items.
	 */
	final static int INT = 3;

	/**
	 * The type of CONSTANT_Float constant pool items.
	 */
	final static int FLOAT = 4;

	/**
	 * The type of CONSTANT_Long constant pool items.
	 */
	final static int LONG = 5;

	/**
	 * The type of CONSTANT_Double constant pool items.
	 */
	final static int DOUBLE = 6;

	/**
	 * The type of CONSTANT_NameAndType constant pool items.
	 */
	final static int NAME_TYPE = 12;

	/**
	 * The type of CONSTANT_Utf8 constant pool items.
	 */
	final static int UTF8 = 1;

	// ------------------------------------------------------------------------
	// Constructors
	// ------------------------------------------------------------------------

	/**
	 * Constructs a new {@link ClassReader} object.
	 *
	 * @param b the bytecode of the class to be read.
	 */
	private ClassReader(final byte[] b) {

	    this(b, 0);
	}

	/**
	 * Constructs a new {@link ClassReader} object.
	 *
	 * @param b   the bytecode of the class to be read.
	 * @param off the start offset of the class data.
	 */
	private ClassReader(final byte[] b, final int off) {

	    this.b = b;
	    // parses the constant pool
	    items = new int[readUnsignedShort(off+8)];
	    int n = items.length;
	    strings = new String[n];
	    int max = 0;
	    int index = off+10;
	    for (int i = 1; i<n; ++i) {
		items[i] = index+1;
		int size;
		switch (b[index]) {
		    case FIELD:
		    case METH:
		    case IMETH:
		    case INT:
		    case FLOAT:
		    case NAME_TYPE:
			size = 5;
		    break;
		    case LONG:
		    case DOUBLE:
			size = 9;
			++i;
		    break;
		    case UTF8:
			size = 3+readUnsignedShort(index+1);
			if (size>max) {
			    max = size;
			}
		    break;
		    // case HamConstants.CLASS:
		    // case HamConstants.STR:
		    default:
			size = 3;
		    break;
		}
		index += size;
	    }
	    maxStringLength = max;
	    // the class header information starts just after the constant pool
	    header = index;
	}

	/**
	 * Constructs a new {@link ClassReader} object.
	 *
	 * @param is an input stream from which to read the class.
	 * @throws IOException if a problem occurs during reading.
	 */
	private ClassReader(final InputStream is) throws IOException {

	    this(readClass(is));
	}

	/**
	 * Reads the bytecode of a class.
	 *
	 * @param is an input stream from which to read the class.
	 * @return the bytecode read from the given input stream.
	 * @throws IOException if a problem occurs during reading.
	 */
	private static byte[] readClass(final InputStream is) throws IOException {

	    if (is==null) {
		throw new IOException("Class not found");
	    }
	    byte[] b = new byte[is.available()];
	    int len = 0;
	    while (true) {
		int n = is.read(b, len, b.length-len);
		if (n==-1) {
		    if (len<b.length) {
			byte[] c = new byte[len];
			System.arraycopy(b, 0, c, 0, len);
			b = c;
		    }
		    return b;
		}
		len += n;
		if (len==b.length) {
		    int last = is.read();
		    if (last<0) {
			return b;
		    }
		    byte[] c = new byte[b.length+1000];
		    System.arraycopy(b, 0, c, 0, len);
		    c[len++] = (byte) last;
		    b = c;
		}
	    }
	}

	// ------------------------------------------------------------------------
	// Public methods
	// ------------------------------------------------------------------------

	/**
	 * Makes the given visitor visit the Java class of this {@link ClassReader}.
	 * This class is the one specified in the constructor (see
	 * {@link #ClassReader(byte[]) ClassReader}).
	 *
	 * @param classVisitor the visitor that must visit this class.
	 */
	private void accept(final TypeCollector classVisitor) {

	    char[] c = new char[maxStringLength]; // buffer used to read strings
	    int i, j, k; // loop variables
	    int u, v, w; // indexes in b

	    String attrName;
	    int anns = 0;
	    int ianns = 0;

	    // visits the header
	    u = header;
	    v = items[readUnsignedShort(u+4)];
	    int len = readUnsignedShort(u+6);
	    w = 0;
	    u += 8;
	    for (i = 0; i<len; ++i) {
		u += 2;
	    }
	    v = u;
	    i = readUnsignedShort(v);
	    v += 2;
	    for (; i>0; --i) {
		j = readUnsignedShort(v+6);
		v += 8;
		for (; j>0; --j) {
		    v += 6+readInt(v+2);
		}
	    }
	    i = readUnsignedShort(v);
	    v += 2;
	    for (; i>0; --i) {
		j = readUnsignedShort(v+6);
		v += 8;
		for (; j>0; --j) {
		    v += 6+readInt(v+2);
		}
	    }

	    i = readUnsignedShort(v);
	    v += 2;
	    for (; i>0; --i) {
		v += 6+readInt(v+2);
	    }

	    // annotations not needed.

	    // visits the fields
	    i = readUnsignedShort(u);
	    u += 2;
	    for (; i>0; --i) {
		j = readUnsignedShort(u+6);
		u += 8;
		for (; j>0; --j) {
		    u += 6+readInt(u+2);
		}
	    }

	    // visits the methods
	    i = readUnsignedShort(u);
	    u += 2;
	    for (; i>0; --i) {
		// inlined in original ASM source, now a method call
		u = readMethod(classVisitor, c, u);
	    }

	}

	private int readMethod(TypeCollector classVisitor, char[] c, int u) {

	    int v;
	    int w;
	    int j;
	    String attrName;
	    int k;
	    int access = readUnsignedShort(u);
	    String name = readUTF8(u+2, c);
	    String desc = readUTF8(u+4, c);
	    v = 0;
	    w = 0;

	    // looks for Code and Exceptions attributes
	    j = readUnsignedShort(u+6);
	    u += 8;
	    for (; j>0; --j) {
		attrName = readUTF8(u, c);
		int attrSize = readInt(u+2);
		u += 6;
		// tests are sorted in decreasing frequency order
		// (based on frequencies observed on typical classes)
		if (attrName.equals("Code")) {
		    v = u;
		}
		u += attrSize;
	    }
	    // reads declared exceptions
	    if (w==0) {
	    } else {
		w += 2;
		for (j = 0; j<readUnsignedShort(w); ++j) {
		    w += 2;
		}
	    }

	    // visits the method's code, if any
	    MethodCollector mv = classVisitor.visitMethod(access, name, desc);

	    if (mv!=null&&v!=0) {
		int codeLength = readInt(v+4);
		v += 8;

		int codeStart = v;
		int codeEnd = v+codeLength;
		v = codeEnd;

		j = readUnsignedShort(v);
		v += 2;
		for (; j>0; --j) {
		    v += 8;
		}
		// parses the local variable, line number tables, and code
		// attributes
		int varTable = 0;
		int varTypeTable = 0;
		j = readUnsignedShort(v);
		v += 2;
		for (; j>0; --j) {
		    attrName = readUTF8(v, c);
		    if (attrName.equals("LocalVariableTable")) {
			varTable = v+6;
		    } else if (attrName.equals("LocalVariableTypeTable")) {
			varTypeTable = v+6;
		    }
		    v += 6+readInt(v+2);
		}

		v = codeStart;
		// visits the local variable tables
		if (varTable!=0) {
		    if (varTypeTable!=0) {
			k = readUnsignedShort(varTypeTable)*3;
			w = varTypeTable+2;
			int[] typeTable = new int[k];
			while (k>0) {
			    typeTable[--k] = w+6; // signature
			    typeTable[--k] = readUnsignedShort(w+8); // index
			    typeTable[--k] = readUnsignedShort(w); // start
			    w += 10;
			}
		    }
		    k = readUnsignedShort(varTable);
		    w = varTable+2;
		    for (; k>0; --k) {
			int index = readUnsignedShort(w+8);
			mv.visitLocalVariable(readUTF8(w+4, c), index);
			w += 10;
		    }
		}
	    }
	    return u;
	}

	/**
	 * Reads an unsigned short value in {@link #b b}. <i>This method is
	 * intended for Attribute sub classes, and is normally not needed by
	 * class generators or adapters.</i>
	 *
	 * @param index the start index of the value to be read in {@link #b b}.
	 * @return the read value.
	 */
	private int readUnsignedShort(final int index) {

	    byte[] b = this.b;
	    return ((b[index]&0xFF)<<8)|(b[index+1]&0xFF);
	}

	/**
	 * Reads a signed int value in {@link #b b}. <i>This method is intended for
	 * Attribute sub classes, and is normally not needed by class
	 * generators or adapters.</i>
	 *
	 * @param index the start index of the value to be read in {@link #b b}.
	 * @return the read value.
	 */
	private int readInt(final int index) {

	    byte[] b = this.b;
	    return ((b[index]&0xFF)<<24)|((b[index+1]&0xFF)<<16)|((b[index+2]&0xFF)<<8)
		    |(b[index+3]&0xFF);
	}

	/**
	 * Reads an UTF8 string constant pool item in {@link #b b}. <i>This method
	 * is intended for Attribute sub classes, and is normally not needed
	 * by class generators or adapters.</i>
	 *
	 * @param index the start index of an unsigned short value in {@link #b b},
	 *              whose value is the index of an UTF8 constant pool item.
	 * @param buf   buffer to be used to read the item. This buffer must be
	 *              sufficiently large. It is not automatically resized.
	 * @return the String corresponding to the specified UTF8 item.
	 */
	private String readUTF8(int index, final char[] buf) {

	    int item = readUnsignedShort(index);
	    String s = strings[item];
	    if (s!=null) {
		return s;
	    }
	    index = items[item];
	    return strings[item] = readUTF(index+2, readUnsignedShort(index), buf);
	}

	/**
	 * Reads UTF8 string in {@link #b b}.
	 *
	 * @param index  start offset of the UTF8 string to be read.
	 * @param utfLen length of the UTF8 string to be read.
	 * @param buf    buffer to be used to read the string. This buffer must be
	 *               sufficiently large. It is not automatically resized.
	 * @return the String corresponding to the specified UTF8 string.
	 */
	private String readUTF(int index, final int utfLen, final char[] buf) {

	    int endIndex = index+utfLen;
	    byte[] b = this.b;
	    int strLen = 0;
	    int c;
	    int st = 0;
	    char cc = 0;
	    while (index<endIndex) {
		c = b[index++];
		switch (st) {
		    case 0:
			c = c&0xFF;
			if (c<0x80) { // 0xxxxxxx
			    buf[strLen++] = (char) c;
			} else if (c<0xE0&&c>0xBF) { // 110x xxxx 10xx xxxx
			    cc = (char) (c&0x1F);
			    st = 1;
			} else { // 1110 xxxx 10xx xxxx 10xx xxxx
			    cc = (char) (c&0x0F);
			    st = 2;
			}
		    break;

		    case 1: // byte 2 of 2-byte char or byte 3 of 3-byte char
			buf[strLen++] = (char) ((cc<<6)|(c&0x3F));
			st = 0;
		    break;

		    case 2: // byte 2 of 3-byte char
			cc = (char) ((cc<<6)|(c&0x3F));
			st = 1;
		    break;
		}
	    }
	    return new String(buf, 0, strLen);
	}

    }

    /***
     * Portions Copyright (c) 2007 Paul Hammant
     * Portions copyright (c) 2000-2007 INRIA, France Telecom
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions
     * are met:
     * 1. Redistributions of source code must retain the above copyright
     *    notice, this list of conditions and the following disclaimer.
     * 2. Redistributions in binary form must reproduce the above copyright
     *    notice, this list of conditions and the following disclaimer in the
     *    documentation and/or other materials provided with the distribution.
     * 3. Neither the name of the copyright holders nor the names of its
     *    contributors may be used to endorse or promote products derived from
     *    this software without specific prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
     * THE POSSIBILITY OF SUCH DAMAGE.
     */

    /**
     * A Java type. This class can be used to make it easier to manipulate type and
     * method descriptors.
     *
     * @author Eric Bruneton
     * @author Chris Nokleberg
     */
    private static class Type {

	/**
	 * The sort of the <tt>void</tt> type.
	 */
	private final static int VOID = 0;

	/**
	 * The sort of the <tt>boolean</tt> type.
	 */
	private final static int BOOLEAN = 1;

	/**
	 * The sort of the <tt>char</tt> type.
	 */
	private final static int CHAR = 2;

	/**
	 * The sort of the <tt>byte</tt> type.
	 */
	private final static int BYTE = 3;

	/**
	 * The sort of the <tt>short</tt> type.
	 */
	private final static int SHORT = 4;

	/**
	 * The sort of the <tt>int</tt> type.
	 */
	private final static int INT = 5;

	/**
	 * The sort of the <tt>float</tt> type.
	 */
	private final static int FLOAT = 6;

	/**
	 * The sort of the <tt>long</tt> type.
	 */
	private final static int LONG = 7;

	/**
	 * The sort of the <tt>double</tt> type.
	 */
	private final static int DOUBLE = 8;

	/**
	 * The sort of array reference types.
	 */
	private final static int ARRAY = 9;

	/**
	 * The sort of object reference type.
	 */
	private final static int OBJECT = 10;

	/**
	 * The <tt>void</tt> type.
	 */
	private final static Type VOID_TYPE = new Type(VOID, null, ('V'<<24)|(5<<16)|(0<<8)|0, 1);

	/**
	 * The <tt>boolean</tt> type.
	 */
	private final static Type BOOLEAN_TYPE = new Type(BOOLEAN, null,
		('Z'<<24)|(0<<16)|(5<<8)|1, 1);

	/**
	 * The <tt>char</tt> type.
	 */
	private final static Type CHAR_TYPE = new Type(CHAR, null, ('C'<<24)|(0<<16)|(6<<8)|1, 1);

	/**
	 * The <tt>byte</tt> type.
	 */
	private final static Type BYTE_TYPE = new Type(BYTE, null, ('B'<<24)|(0<<16)|(5<<8)|1, 1);

	/**
	 * The <tt>short</tt> type.
	 */
	private final static Type SHORT_TYPE = new Type(SHORT, null, ('S'<<24)|(0<<16)|(7<<8)|1, 1);

	/**
	 * The <tt>int</tt> type.
	 */
	private final static Type INT_TYPE = new Type(INT, null, ('I'<<24)|(0<<16)|(0<<8)|1, 1);

	/**
	 * The <tt>float</tt> type.
	 */
	private final static Type FLOAT_TYPE = new Type(FLOAT, null, ('F'<<24)|(2<<16)|(2<<8)|1, 1);

	/**
	 * The <tt>long</tt> type.
	 */
	private final static Type LONG_TYPE = new Type(LONG, null, ('J'<<24)|(1<<16)|(1<<8)|2, 1);

	/**
	 * The <tt>double</tt> type.
	 */
	private final static Type DOUBLE_TYPE = new Type(DOUBLE, null, ('D'<<24)|(3<<16)|(3<<8)|2,
		1);

	// ------------------------------------------------------------------------
	// Fields
	// ------------------------------------------------------------------------

	/**
	 * The sort of this Java type.
	 */
	private final int sort;

	/**
	 * A buffer containing the internal name of this Java type. This field is
	 * only used for reference types.
	 */
	private char[] buf;

	/**
	 * The offset of the internal name of this Java type in {@link #buf buf} or,
	 * for primitive types, the size, descriptor and getOpcode offsets for this
	 * type (byte 0 contains the size, byte 1 the descriptor, byte 2 the offset
	 * for IALOAD or IASTORE, byte 3 the offset for all other instructions).
	 */
	private int off;

	/**
	 * The length of the internal name of this Java type.
	 */
	private final int len;

	// ------------------------------------------------------------------------
	// Constructors
	// ------------------------------------------------------------------------

	/**
	 * Constructs a primitive type.
	 *
	 * @param sort the sort of the primitive type to be constructed.
	 */
	private Type(final int sort) {

	    this.sort = sort;
	    this.len = 1;
	}

	/**
	 * Constructs a reference type.
	 *
	 * @param sort the sort of the reference type to be constructed.
	 * @param buf  a buffer containing the descriptor of the previous type.
	 * @param off  the offset of this descriptor in the previous buffer.
	 * @param len  the length of this descriptor.
	 */
	private Type(final int sort, final char[] buf, final int off, final int len) {

	    this.sort = sort;
	    this.buf = buf;
	    this.off = off;
	    this.len = len;
	}

	/**
	 * Returns the Java types corresponding to the argument types of the given
	 * method descriptor.
	 *
	 * @param methodDescriptor a method descriptor.
	 * @return the Java types corresponding to the argument types of the given
	 *         method descriptor.
	 */
	private static Type[] getArgumentTypes(final String methodDescriptor) {

	    char[] buf = methodDescriptor.toCharArray();
	    int off = 1;
	    int size = 0;
	    while (true) {
		char car = buf[off++];
		if (car==')') {
		    break;
		} else if (car=='L') {
		    while (buf[off++]!=';') {
		    }
		    ++size;
		} else if (car!='[') {
		    ++size;
		}
	    }

	    Type[] args = new Type[size];
	    off = 1;
	    size = 0;
	    while (buf[off]!=')') {
		args[size] = getType(buf, off);
		off += args[size].len+(args[size].sort==OBJECT ? 2 : 0);
		size += 1;
	    }
	    return args;
	}

	/**
	 * Returns the Java type corresponding to the given type descriptor.
	 *
	 * @param buf a buffer containing a type descriptor.
	 * @param off the offset of this descriptor in the previous buffer.
	 * @return the Java type corresponding to the given type descriptor.
	 */
	private static Type getType(final char[] buf, final int off) {

	    int len;
	    switch (buf[off]) {
		case 'V':
		    return VOID_TYPE;
		case 'Z':
		    return BOOLEAN_TYPE;
		case 'C':
		    return CHAR_TYPE;
		case 'B':
		    return BYTE_TYPE;
		case 'S':
		    return SHORT_TYPE;
		case 'I':
		    return INT_TYPE;
		case 'F':
		    return FLOAT_TYPE;
		case 'J':
		    return LONG_TYPE;
		case 'D':
		    return DOUBLE_TYPE;
		case '[':
		    len = 1;
		    while (buf[off+len]=='[') {
			++len;
		    }
		    if (buf[off+len]=='L') {
			++len;
			while (buf[off+len]!=';') {
			    ++len;
			}
		    }
		    return new Type(ARRAY, buf, off, len+1);
		    // case 'L':
		default:
		    len = 1;
		    while (buf[off+len]!=';') {
			++len;
		    }
		    return new Type(OBJECT, buf, off+1, len-1);
	    }
	}

	// ------------------------------------------------------------------------
	// Accessors
	// ------------------------------------------------------------------------

	/**
	 * Returns the number of dimensions of this array type. This method should
	 * only be used for an array type.
	 *
	 * @return the number of dimensions of this array type.
	 */
	private int getDimensions() {

	    int i = 1;
	    while (buf[off+i]=='[') {
		++i;
	    }
	    return i;
	}

	/**
	 * Returns the type of the elements of this array type. This method should
	 * only be used for an array type.
	 *
	 * @return Returns the type of the elements of this array type.
	 */
	private Type getElementType() {

	    return getType(buf, off+getDimensions());
	}

	/**
	 * Returns the name of the class corresponding to this type.
	 *
	 * @return the fully qualified name of the class corresponding to this type.
	 */
	private String getClassName() {

	    switch (sort) {
		case VOID:
		    return "void";
		case BOOLEAN:
		    return "boolean";
		case CHAR:
		    return "char";
		case BYTE:
		    return "byte";
		case SHORT:
		    return "short";
		case INT:
		    return "int";
		case FLOAT:
		    return "float";
		case LONG:
		    return "long";
		case DOUBLE:
		    return "double";
		case ARRAY:
		    StringBuffer b = new StringBuffer(getElementType().getClassName());
		    for (int i = getDimensions(); i>0; --i) {
			b.append("[]");
		    }
		    return b.toString();
		    // case OBJECT:
		default:
		    return new String(buf, off, len).replace('/', '.');
	    }
	}
    }
}
